package com.skillworld.webapp.model.user;


import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

import com.skillworld.webapp.model.award.Award;
import com.skillworld.webapp.model.bag.Bag;
import com.skillworld.webapp.model.course.Course;
import com.skillworld.webapp.model.department.Department;
import com.skillworld.webapp.model.department.rank.DeptRank;
import com.skillworld.webapp.model.department.rank.DeptRankDerived;
import com.skillworld.webapp.model.gamemap.GameMap;
import com.skillworld.webapp.model.gamemap.Position;
import com.skillworld.webapp.model.team.Team;
import com.skillworld.webapp.model.tutorial.MarkedTutorial;

@Entity
@org.hibernate.annotations.BatchSize(size=10)
@Table(name="User")
public class User 
{
    public enum Role { MASTERCOACH, STUDENT }   
    
	private Long userId;
	private String nickname;
	private String skilly;
	private String picture;
	private String facebookId;
	private Bag bag;
	private int totalPoints;
	private Map<Long,DeptRank> departmentRanks
	    = new HashMap<Long,DeptRank>();
	private GameMap currentMap;
	private Position lastPosition;
	private Set<Team> teams = new HashSet<Team>();
	private Set<Award> awards = new HashSet<Award>();
	private UserDerived derived;


	protected User() 
	{

    }

    public User(String nickname, String skilly, String picture,
                String facebookId, int initialBagCapacity, 
                GameMap currentMap)
	{
		this.nickname = nickname;
		this.skilly = skilly;
		this.picture = picture;
		this.facebookId = facebookId;
		// @createBag@ manages the bi-directional relationship
		createBag(initialBagCapacity);
		this.totalPoints = 0;
		joinMap(currentMap);
		this.derived = null;
	}

    
	@SequenceGenerator(name="UserIdGenerator",sequenceName="UserSeq")
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO,generator="UserIdGenerator")
	@Column(name="idUser")
	public Long getUserId() {
		return userId;
	}
	   
	
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	
	public String getNickname() {
		return nickname;
	}
	
	public void setNickname(String nickname) {
		this.nickname = nickname;
	}

	@Column(name="idSkin")
	public String getSkilly() {
		return skilly;
	}

	public void setSkilly(String skilly) {
		this.skilly = skilly;
	}

	@Column(name="picture")
	public String getPicture() {
        return picture;
    }

    public void setPicture(String picture) {
        this.picture = picture;
    }

    @Column(name="idFacebook")
	public String getFacebookId() {
		return facebookId;
	}
	
	public void setFacebookId(String facebookId) {
		this.facebookId = facebookId;
	}

	@OneToOne(optional=false,fetch=FetchType.LAZY)
	@JoinColumn(name="idInventory")
	@Cascade(CascadeType.SAVE_UPDATE)
	public Bag getBag() {
		return bag;
	}

	protected void setBag(Bag bag) {
		this.bag = bag;
	}
	
	private void createBag(int bagCapacity)
	{
	    bag = new Bag(bagCapacity);
	    bag.setUser(this);
	}
	
	public int getTotalPoints() {
        return totalPoints;
    }

    protected void setTotalPoints(int totalPoints) {
        this.totalPoints = totalPoints;
    }
    
    // TODO Persist medals and update then when the user gets points
    @Transient
    public Medals getMedals()
    {
        return new Medals(getDepartmentRanks().values());
    }

    @OneToMany(fetch=FetchType.LAZY,mappedBy="user",orphanRemoval=true)
    @MapKeyColumn(name="idDepartment")
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.DELETE})
	public Map<Long,DeptRank> getDepartmentRanks() {
        return departmentRanks;
    }

    protected void setDepartmentRanks(Map<Long, DeptRank> departmentranks) {
        this.departmentRanks = departmentranks;
    }

    @Transient
    public DeptRank getDepartmentRank(long deptId) 
    {
        return getDepartmentRanks().get(deptId);
    }
    
    @Transient
    public DeptRank getDepartmentRank(Department department)
    {
        DeptRank deptRank = getDepartmentRank(department.getDepartmentId());
        
        if (deptRank == null)
        { 
            deptRank = new DeptRank(this, department);
            getDepartmentRanks().put(department.getDepartmentId(), deptRank);
        }
        
        return deptRank;
    }
    
    public void addPoints(int points, Department department)
    {
        DeptRank deptRank = getDepartmentRank(department);
        deptRank.addPoints(points);
        totalPoints += points;
    }

    @ManyToOne(optional=false,fetch=FetchType.LAZY)
	@JoinColumn(name="idMap")
	public GameMap getCurrentMap() {

		return currentMap;
	}

	protected void setCurrentMap(GameMap currentMap) {
		this.currentMap = currentMap;
	}
	
	public void joinMap(GameMap gameMap)
	{
	    setCurrentMap(gameMap);
	    setLastPosition(gameMap.getInitPosition());
	}

	@Embedded
	@AttributeOverrides({
        @AttributeOverride(name="x",column=@Column(name="lastX")),
        @AttributeOverride(name="y",column=@Column(name="lastY"))
		})
	public Position getLastPosition() {
		return lastPosition;
	}

	public void setLastPosition(Position lastPosition) {
		this.lastPosition = lastPosition;
	}
	
	/*
	 * TO-DO: (Re-)Define global level and grade
	 */
	@Transient
	public int getGlobalLevel()
	{
		int sum = 0;
		for(DeptRank dept : departmentRanks.values())
		{ sum += dept.getLevel(); }
		
		return sum;
	}

	@ManyToMany(mappedBy="members")
    public Set<Team> getTeams() {
        return teams;
    }

    protected void setTeams(Set<Team> teams) {
        this.teams = teams;
    }
    
    public void joinTeam(Team team)
    {
        this.getTeams().add(team);
        team.getMembers().add(this);
    }

    public void leaveTeam(Team team)
    {
        this.getTeams().remove(team);
        team.getMembers().remove(this);
    }

    @ManyToMany(fetch=FetchType.LAZY)
    @JoinTable(name="AwardUser",
     joinColumns=@JoinColumn(name="idUser"),
     inverseJoinColumns=@JoinColumn(name="idAward")
    )
    public Set<Award> getAwards() {
        return awards;
    }

    protected void setAwards(Set<Award> awards) {
        this.awards = awards;
    }

    public void addAward(Award award)
    {
        this.getAwards().add(award);
    }

    @OneToOne(optional=true,fetch=FetchType.LAZY)
    @JoinColumn(name="idUser")
    public UserDerived getDerived()
    {
        return derived;
    }

    protected void setDerived(UserDerived derived) 
    {
        this.derived = derived;
    }
}
